某交付型 ERP 公司的 Lowcode 演进史
过去的十个月,我以项目技术负责人的身份,成功将整个 Lowcode
项目推向了 Release。期间遇到了很多的技术选择问题,同样也遇到了很多项目推进中会发生的,与技术无关的问题。这里做一个简单的总结,希望遗留下来的经验对后来者有所帮助。
交付公司为什么会做 Lowcode?
作为一家交付型 ERP 公司,为什么你们会希望做一款
Lowcode
产品呢?
说起公司做 Lowcode
的原因,其实上也挺简单的。一个核心思想:节约开销。
可能很多人不理解,我司作为一家 ERP 厂商的实施方,为什么会有节约开销这种需求?这一点算是历史遗留问题,简单来说可以被总结为两个点:
- 交付团队人员变化快,接手人面对各式各样的技术会陷入迷茫,从而延长交付时间
- 客户需求变化快,交付团队有时会被拖入需求频繁变动的拉锯战中
我司作为一家以交付项目为主体的公司,客户需求自然是很难被直接管控的点,因此能够入手的地方也就只有 1 了,从人员结构和技术框架开始。
切入点 / 面向人群
让我们先来看看一个标准交付团队的人员组成:产品 + 后端 + 前端
再来看看公司内目前标准交付项目的技术栈组成:Mobx
/ Redux
+ React
+ Java
(后端技术栈 etc)+ Dva
(中台交付) / React-Router
(研发交付)
可以看到,上述的技术栈组成中,最为繁杂的就是前端的各种库,这部分如非高级前端的话,很难短期内掌握。其次,交付团队中需要前端也是近几年前端技术栈复杂后的结果。
自然而然的,我们会提出一个问题:交付团队中真的需要高级前端的支撑吗?
交付团队中的高级前端,能否被其他两类角色代替?
从这个角度出发去思考问题,我们会很惊讶的发现,目前公司交付项目上的业务普遍由单一的 CRUD
组成。而高级前端承接的任务,更多的是对前端项目进行配置,或者是使用后端不太了解的技术栈(Redux
/ React-Router
)去搭建页面。这样说的话,我们需要关注的,是对这些前端特有的技术栈进行相关的封装,让后端也能完成前端开发所能完成的事情。
因此得出结论,我们的切入点在于取代高级前端,而面向的人群,是交付项目上另外两类角色。公司最终的愿景,在于将整个交付团队的人员削减到只有业务 / 产品的地步。
第一阶段
搞清楚面向人群后,第一阶段的产物就是 DataSet
。这是一套基于 Mobx
、Axios
,封装了业务中常用 CRUD
逻辑的一套抽象产物。与典型的业务组件相搭配(如 Form / Table),从而让后端也能顺利的书写前端代码。它的前身是 JSP 时代,我司内部自行抽象的 Aurora。
DataSet
这套产物,目前已经在全面服务我司内部的各种交付项目,并实现了第一个愿景,让后端代替前端的工作。它的出现让传统 CRUD
逻辑有了管控的地方,以及大大简化了前端代码书写的复杂程度。后端不必再和 Redux
这种前端组件库打交道,可以直接将自己很熟悉的数据库表结构套用到 DataSet
这套体系中。
同样的,DataSet
也是我即将提到的第二阶段,Lowcode
平台中模型的重要组成部分。
第二阶段
第一阶段中,我们让后端代替了前端,那么是否存在更进一步的方案,让项目中的产品 / 业务去替代后端的职责,从而大幅减少项目人员成本呢?
我司目前在交付项目上最常见的场景,便是头行表单。简而言之就是 Form + Table
:Table
选中的行有变动的时候,上面的 Form
也会随着变动,而且在 Form
中修改后的数据也会被同步到下面的 Table
中。
上述的这种场景算是简单场景之一,那么我们是否具备复用这种简单场景的能力呢?
如果我们有一个平台,可以用可视化的手段实现这些逻辑,并且可以通过某种手段,在下一次的搭建过程中复用上一次的搭建产物,这样不就可以回答上面这个问题了吗?
聪明的人一定会猜到,这个平台本身就是 Lowcode
。Lowcode
作为可视化的搭建平台的重要作用就是收束之前项目的搭建产物,并使之能够在下一次的搭建流程中被复用,而可视化拖拽,则是为了降低交互门槛所采用的一种交互手段。
搭建这个平台的过程中势必会碰到各种各样的问题,经过 10 个月的踩坑,我将典型的问题归结为以下几种:
动态表单
在 Lowcode
刚起步的时候,左侧选中不同的组件,如何高效且有效的切换组件对应的属性面板?同时不让开发者进行反复的开发工作(比如每新建一个组件,就要书写一个对应的业务表单)?
这里需要感谢一下 gaea-editor,观察了他们的属性面板设计后,我发现属性面板实际上就是开发中存在很久的一个问题:可配置化的动态表单。要解决这一点,需要的是一个基于 IOC
设计模式的架构(类似 vscode
),将每个组件中使用的公共组件抽离成插件,每个组件维护一套 config
数组,当选中的组件被切换的时候,切换到对应的 config
,并依照这套规则去加载对应的插件,从而组合成一套全新的表单。
不过这里还是存在另外一个问题:
如果我们希望在未来加入 npm 组件导入功能,开发者书写的组件是多种多样的,如何根据他们书写的组件识别出对应的 config 呢?
熟悉 Lowcode
的同学应该就看出来了,这套设计在 云凤蝶 中出现过。起初我们最直观的想法就是:开发者自己去书写。这套思路虽然可行,但是会增加开发者接入 Lowcode
平台的复杂度,降低他们的接入意愿,因此这套流程应当是尽可能自动化的才对。
这个简单的问题整整困扰了我近半年时间,期间我出过各种各样的设计思路,但是都不尽如人意。直到年中的时候,同组的后端向我们抱怨前端组件的 json
在后端难以维护,希望给一套方案去校验的时候,上述这个问题才迎刃而解。
目前对于组件经常变动的属性数据,后端是直接用 json
字符串的形式去存储的,每次升级组件的时候,json
数据的升级都会是一个大问题。因为组件的属性是经常增删的,如果某些关键属性在后端的升级脚本中没有注意到的话,整个平台都会存在着崩溃的风险。这对于整个平台的维护并非是一个好消息。
起初我的想法也很简单,后端针对 json
所有的数据落一张表,然后 json
数据存到后端的时候,直接对照这张表去做校验即可。但是仔细深入思考一下,会出现一个很奇怪的问题:json
发展历程如此之久,真的没有人遇到跟我们一样的校验问题吗?
带着这个问题,我去查阅了社区对应的实现,最后找到了这个 keyPoint:json-schema
。
json-schema 用于描述并校验 json
格式,它类似一套结构化的规范,前端和后端均可使用 json-schema
去做 json
格式的校验。如果我们能够对 json
书写一套完备的 json-schema
的话,前后端自然就能省去数据校验不准确的各类问题了。
那么它和动态表单有什么联系呢?我们可以仔细看看 json-schema
的结构,并与 gaea-editor
的 config
做一下对比
json-schema
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"name": { "type": "string" },
"email": { "type": "string" },
"age": {
"type": "integer",
"minimum": 0,
"exclusiveMinimum": false,
},
"telephone": {
"type": "string",
"pattern": "^(\\([0-9]{3}\\))?[0-9]{3}-[0-9]{4}$"
}
},
"required": ["name", "email"],
"additionalProperties": false
}
gaea-editor
const editSetting = {
key: 'gaea-button',
name: 'Button',
editors: [
'Layout',
{
type: 'box-editor'
},
'Function',
{
field: 'text',
text: 'Text',
type: 'string'
},
{
field: 'href',
text: 'Href',
type: 'string'
},
'Style',
{
field: 'ghost',
text: 'Transparent',
type: 'boolean'
},
{
field: 'size',
text: 'Size',
type: 'select',
data: [{
value: null,
text: 'Default'
},
{
value: 'small',
text: 'Small'
},
{
value: 'large',
text: 'Large'
}
]
}
],
events: [{
text: 'OnClick',
field: 'onClick'
}]
};
可以发现,两者之间还是存在不少共性的,那么紧接着会迎来另一个问题,json-schema
如果能直接生成表单的话,那我们岂不就不用单独维护一套属性面板的 config
吗?
这部分在业内也有很多的常用实现,我主要研究的是 form-renderer,这是利用 json-schema
与自己的特有属性结合实现的一套专有引擎。同样基于 IOC
,只不过 config
换成了 json-schema
而已。
再仔细看看 json-schema
,是否觉得这套东西跟某种东西有所共通呢?没错,就是 typescript
中的 interface
。interface
跟 json-schema
做的事情也非常类似,都是针对某中东西做类型规约和校验。
如果我们把组件库中针对组件 Props 的 interface 提取出来转换成 json-schema,是不是就可以在不写一行代码的情况下,自动读取并生成属性面板呢?
借助 typescript-json-schema,我们可以将 typescript
组件库中的 interface
转换成 json-schema
,然后利用 form-renderer
这类库翻译成对应的属性面板,这样就解决了上面的问题。
布局
布局这一点,不同的 Lowcode
业务会有不同的实现方式。如 云凤蝶 采用了自由画布,并且在这个特性上投入了大量资源。而 Mendix 这一类产品,就将研发的重点投入到了栅格布局上。因此实现如何,主要看的还是业务平台本身面向的人群。
因为我们能够投入的研发力量有限,不能像云凤蝶一样在画布方面大量投入技术资源,因此我们选择了更好实现的栅格布局作为我们的实现手段。
栅格布局的实现,整体来说也不算很难,需要关注的就是一个点即可:组件渲染逻辑。
我们在平台中规定了基础组件、数据组件以及容器组件这三种类型,然后将整个组件渲染逻辑做一次完整的规约,其中数据组件可以被理解为与表绑定的组件,基础组件则对应的是数据库表中的字段(所以基础组件不能独立于数据组件而单独存在),而容器组件,顾名思义就是用来承载上述两种组件的组件。同样的,容器组件也可以递归式的承载(即容器组件可以相互嵌套)。
确立了上述的渲染逻辑后,整个栅格布局就如同水到渠成,剩下的就是投入时间和精力去一一实现其中的各项功能了。
另外还有一个点会是组件树,组件树是为了解决组件相互嵌套后,用户如何能够通过便捷的操作去选中某一层级的组件这一需求的。有了渲染逻辑,只需要根据逻辑写一个简单的 dfs
就可以做到这一切。
当然,这并不是代表着我对自由画布不感兴趣。有机会的话,我还是希望能够探索一下自由画布的详细实现细节的。
逻辑编排
市面上大部分的 Lowcode
平台都有一个奇怪的问题,不重视逻辑编排功能,实际上我对这部分的实现一直非常感兴趣。一个成熟的 Lowcode
产品,必然会承载一些 CRUD
逻辑以外的东西,如何承载并维护好这些沉淀下来的业务逻辑,对我们来说是一个新的挑战。
针对产品面向的人群,以及我们目前能够投入的技术力量综合考评来看,我们采用了工作流式的实现方案,并且实现了一套前后端节点可以交替执行的特殊执行逻辑(由于涉密,这里不做详细展开)。
但是这不代表着工作流式的实现方案就是唯一的解,至少我见过的实现方式就有两套,这里我简单列一下:
这是两套不太一样的逻辑编排思路,第二种非常类似伪代码的实现,这套思路很适合面向人群非开发的群体,但是对于我们这种面向人群中存在产品的就不太适合。
因此,这一块的交互选型,依然是要根据产品使用情况,具体情况具体分析。
其实上还是有一些很有意思的点可以深挖的:比如 for
循环在面向开发和面向非开发时应该选择什么表现形式?以及逻辑编排中是否应该引入变量这一概念?甚至说是否应该在引擎执行时对 AST 树进行分析并优化?如何针对逻辑 debug
引入一套完善的体系?这些点都是值得深入发掘并深究的,以后有机会的话,会再输出一篇文章讲这件事情。
产品的未来规划
目前我为整个 Lowcode
项目组列下的计划中,主要包含以下几个方向:
- 移动端
Lowcode
- 业务组件整合平台
Lowcode
使用情况监控
第一个方向挺简单的,Boss 希望 Lowcode
能够同时具备移动端和 pc 端的搭建能力,在配合已经抽象好的 DataSet
基础之上,实现的话也就是更换渲染引擎与设计器而已,挑战点会集中在渲染引擎部分(如何将 json 数据翻译成移动端能识别的数据)
第二个方向是我力主要完成的点,这个平台主要是为了解决一个核心问题:项目交付人员如何复用之前交付时的交付产物。copy paste
作为一种方案当然可行,问题在于低效,因此我希望能够有一种平台,可以将所有这些交付产物管理起来,并在下一次交付的时候能够快速的开箱即用。
第三个方向是为了补足我们目前存在的问题,项目很多的特性都是参照其他的产品实现去做的,但是这些特性是否真的适合我们自身的产品?衡量指标是什么?客户是否真的对这些特性买账?这些都是我们未来需要回答的问题。
小结
记得上个月看到 Thoughtworks 的技术负责人痛批 Lowcode,在我看来实属用力过度了。这类产品的意义从来不在于通用,而是解决某中特定场景下的交付痛点。观察目前我们试点项目的反馈情况,简单场景下的交付还是具备不错的反响的。因此这一类产品,一定是结合公司的个体情况去做对应的设计,才能取得好的反响。
同样的,从这个角度去看问题,可以发现 Lowcode
像是一套公司资产的收集器,将过去公司交付所产生的资产做一个统一的规约,从而利用公司过去所忽视的这些交付产物形成一个生态,在自己的业务领域打出独有的优势。这就是 Lowcode
这类产品到目前为止依然层出不穷的原因所在。
最后总结一下,Lowcode 的提效从来不在于拖拽,也不在于可视化,一切提效的手段本质上是简化操作,以及对之前成果的复用,仅此而已。
杂谈
其实上我们整个团队,在制作 Lowcode
的时候一直都在思考一个问题:
如果我司的低代码平台成熟了,希望用业务代替所有研发的愿景也实现了,那我们的交付人员应当何去何从呢?
如果一个业务能用
Lowcode
平台稳定交付项目,这时他能否被称作是程序员?
这些问题都很具备思考价值,大公司也许不需要去考虑这种问题,因为人员素质足够高,被解放出来的人力资源可以投入更有价值的研究工作中。但是像我们这样的小公司,交付人员在研发体系成熟后应当何去何从?是否会像纺织机发明后失业的纺织工一样呢?
这些问题我也暂且没有思考出明确的答案,也许未来的某一天会得到真正的答案吧,届时我会再更新一篇后续文章的。